import Vue,{ComponentOptions,FunctionalComponentOptions} from 'vue';
/*************************** */
export type Prop<T> = { (): T } | { new (...args: any[]): T & object }
type DataDef<Data, Props, V> = Data | ((this: Readonly<Props> & V) => Data)
export interface PropOptions<T=any> {
  type?: Prop<T> | Prop<T>[];
  required?: boolean;
  default?: T | null | undefined | (() => object);
  validator?(value: T): boolean;
}

export type PropValidator<T> = PropOptions<T> | Prop<T> | Prop<T>[];
export type RecordPropsDefinition<T> = {
  [K in keyof T]: PropValidator<T[K]>
}
export type CombinedVueInstance<Instance extends Vue, Data, Methods, Computed, Props> =  Data & Methods & Computed & Props & Instance;
/**
 * This type should be used when an array of strings is used for a component's `props` value.
 */
export type ThisTypedComponentOptionsWithArrayProps<V extends Vue, Data, Methods, Computed, PropNames extends string> =
  object &
  ComponentOptions<V, DataDef<Data, Record<PropNames, any>, V>, Methods, Computed, PropNames[], Record<PropNames, any>> &
  ThisType<CombinedVueInstance<V, Data, Methods, Computed, Readonly<Record<PropNames, any>>>>;

/**
 * This type should be used when an object mapped to `PropOptions` is used for a component's `props` value.
 */
export type ThisTypedComponentOptionsWithRecordProps<V extends Vue, Data, Methods, Computed, Props> =
  object &
  ComponentOptions<V, DataDef<Data, Props, V>, Methods, Computed, RecordPropsDefinition<Props>, Props> &
  ThisType<CombinedVueInstance<V, Data, Methods, Computed, Readonly<Props>>>;
/***********************************/  
type append<ModuleData,Events,Data> = {
  module:ModuleDef<ModuleData,Events>
  init:(module:ModuleData)=>Data
}
type trigger<T> = {
  trigger:T
}

declare module "vue/types/vue" {
  interface Vue {
    $events:EventClass
  }
  interface VueConstructor<V extends Vue = Vue> {
    proxyVue<ModuleData,Events,Data, Methods, Computed, PropNames extends string = never>(options?: append<ModuleData,Events,Data>&ThisTypedComponentOptionsWithArrayProps<V, Data&trigger<Events>, Methods, Computed, PropNames>): ExtendedVue<V, Data, Methods, Computed, Record<PropNames, any>>;
    
    proxyVue<ModuleData,Events,Data, Methods, Computed, Props>(options?: append<ModuleData,Events,Data>&ThisTypedComponentOptionsWithRecordProps<V, Data&trigger<Events>, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>;
    
    proxyVue<PropNames extends string = never>(definition: FunctionalComponentOptions<Record<PropNames, any>, PropNames[]>): ExtendedVue<V, {}, {}, {}, Record<PropNames, any>>;

    proxyVue<Props>(definition: FunctionalComponentOptions<Props, RecordPropsDefinition<Props>>): ExtendedVue<V, {}, {}, {}, Props>;
    
    // proxyVue<ModuleData,Events,Data>(options?: append<ModuleData,Events,Data>&ComponentOptions<V,DataDef<Data&trigger<Events>, {}, V>>): ExtendedVue<V, {}, {}, {}, {}>;
  }
}
const ObjConstant = "__obj__";
function proxyFunc(type:string,v:any){
  return function(this:any){
    return v[ObjConstant][type].apply(v[ObjConstant],arguments);
  }
}


Vue.proxyVue = function(cfg:any){
  cfg.props = {
      space:{
        type:String
      },
      ...(cfg.props||{})
  }
  const initData = cfg.init;
  let v:any;
  const trigger:{[key:string]:Function} = {};
  cfg.data = function(){
    v = cfg.module.data.call(this);
    const obj:{[key:string]:any} = initData(v);

    const dataInfo = {
      ...obj,
      [ObjConstant]:v,
      trigger
    };

    Object.keys(cfg.module.events).forEach(m=>{
      trigger[m] = proxyFunc(m,dataInfo);
      v[m] = cfg.module.events[m];
    })
    return dataInfo;
  }
  cfg.mounted=()=>{
    cfg.module.onShow.call(v);
  }
  cfg.beforeMount=()=>{
    cfg.module.onReady.call(v);
  };
  cfg.destroyed=()=>{
    cfg.module.onDestroy.call(v);
  }
  cfg.created=()=>{
    cfg.module.onCreate.call(v);
  }
  return Vue.extend(cfg);
}
//onCreate,onDestroy,onReady,onRefresh,onShow